#include <stdio.h>
#include <string.h>
#if defined(TEST_TARGET_intl)
#include <varch/command.h>
#include <varch/unitt.h>
#include <varch/intl.h>
#else  
#include "init.h"
#include "command.h"
#include "unitt.h"
#include "kern.h"
#include "intl.h"
#endif

static char pbuff[INTL_PRINT_MAX] = {0};

/************************************************************************************/
/************************************* Unit Test ************************************/
/************************************************************************************/

// #define EXIT_TEST
extern uint64_t unitt_clock(void);

static intl random_intl()
{
    intl random = INTL_ZERO;

    for (int i = 0; i < __INTL_U32_PARTS__; i++) 
    {
        random.u32[i] = rand();
    }

    return random;
}

static const char* random_format()
{
    const char *format[4] = { "d", "#b", "#o", "#x" };
    return format[rand() % 4];
}

static int test_convert(void)
{
    for (int i = 0; i < 100; i++)
    {
        intl src = random_intl();
        intl dst = intl_from(intl_show(src, pbuff, sizeof(pbuff), random_format()));

        if (!intl_eq(src, dst)) 
        {
            printf("convert fail: \r\n");
            printf("src: %s\r\n", intl_show(src, pbuff, sizeof(pbuff), "x"));
            printf("dst: %s\r\n", intl_show(dst, pbuff, sizeof(pbuff), "x"));
            #if defined (EXIT_TEST)
            exit(0);
            #endif 
            return UNITT_E_FAIL;
        }
    }
    
    return UNITT_E_OK;
}

/* Need config intl as 64bits */
static int test_accuracy(void)
{
    for (int i = 0; i < 1000; i++)
    {
        int op = rand() % 10;
        uint8_t sh = 0;

        intl a = random_intl();
        intl b = random_intl();
        intl c = INTL_ZERO;
        
        int64_t x = 0;
        int64_t y = 0;
        int64_t z = 0;

        memcpy(&x, &a, sizeof(x));
        memcpy(&y, &b, sizeof(y));

        /* Skip the division by 0 */
        if (op == 3 && y == 0) continue;
        /* Adjust shift */
        if (op == 8 || op == 9) { sh = y; sh %= 64; }

        if      (op == 0) { c = intl_add(a, b);        z = x + y; }
        else if (op == 1) { c = intl_sub(a, b);        z = x - y; }
        else if (op == 2) { c = intl_mul(a, b);        z = x * y; }
        else if (op == 3) { c = intl_div(a, b);        z = x / y; }
        else if (op == 4) { c = intl_mod(a, b);        z = x % y; }
        else if (op == 5) { c = intl_and(a, b);        z = x & y; }
        else if (op == 6) { c = intl_xor(a, b);        z = x ^ y; }
        else if (op == 7) { c = intl_or (a, b);        z = x | y; }
        else if (op == 8) { c = intl_shl(a, sh);        z = x << sh; }
        else if (op == 9) { c = intl_shr(a, sh);        z = x >> sh; }
        
        if (memcmp(&c, &z, sizeof(z)) != 0) 
        {
            printf("accuracy fail: %d,%d\r\n", op, sh);
            printf("c: %s\r\n", intl_show(c, pbuff, sizeof(pbuff), "d"));
            printf("z: %lld\r\n", z);
            #if defined (EXIT_TEST)
            exit(0);
            #endif 
            return UNITT_E_FAIL;
        }
    }
    
    return UNITT_E_OK;
}

static void unitt_task(void)
{
    static UNITT_TCASE rand_tests[] = {
        UNITT_TCASE(test_convert),
#if defined (INTL_USE_64BITS)
        UNITT_TCASE(test_accuracy),
#endif 
    };

    static UNITT suites[] = {
        { "intl suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock },
    };

    UNITT_EXE(suites);
}

/************************************************************************************/
/************************************* Base Test ************************************/
/************************************************************************************/

void intl_gen_cfg(uint32_t bits, const char *filename)
{
    #define MINBITS 64
    #define NEWLINE "\n" // "\r\n" // 

    FILE* output = stdout; // Default output to standard output
    if (filename != NULL) 
    {
        output = fopen(filename, "w");
        if (output == NULL) 
        {
            fprintf(stdout, "[ERROR] Failed to open output file"NEWLINE);
            return;
        }
    }

    /* The number of bits in an intl needs to be an exponent of two */
    if ((bits & (bits - 1)) != 0)
    {
        fprintf(stdout, "[ERROR] `bits` not a power of 2"NEWLINE);
        return;
    }

    /* Limiting the minimum bit */
    if (bits < MINBITS)
    {
        fprintf(stdout, "[ERROR] `bits` too small\r\b");
        return;
    }

    fprintf(output, "/*****************************************************************/"NEWLINE);
    fprintf(output, "/*                          Config start                         */"NEWLINE);
    fprintf(output, "/*****************************************************************/"NEWLINE);

    for (uint32_t tb = MINBITS; tb <= bits; tb <<= 1)
    {
        if (tb == bits) 
        {
            fprintf(output, "#define INTL_USE_%uBITS"NEWLINE, tb);
        }
        else  
        {
            fprintf(output, "// #define INTL_USE_%uBITS"NEWLINE, tb);
        }
    }
    fprintf(output, ""NEWLINE);

    intl temp;

    for (uint32_t tb = MINBITS; tb <= bits; tb <<= 1)
    {
        if (tb == MINBITS) 
        {
            fprintf(output, "#if defined(INTL_USE_%uBITS)"NEWLINE, tb);
        }
        else  
        {
            fprintf(output, "#elif defined(INTL_USE_%uBITS)"NEWLINE, tb);
        }

        uint32_t BIT_PARTS ; /* bits */
        uint32_t U32_PARTS ; /* __INTL_BIT_PARTS__ / 32 */
        uint32_t U16_PARTS ; /* __INTL_BIT_PARTS__ / 16 */
        uint32_t MAX_BIN   ; /* __INTL_BIT_PARTS__ + 1 */
        uint32_t MAX_DEC   ; /* strlen(intl_shl(intl(1), __INTL_BIT_PARTS__ - 1)) + 1 */
        uint32_t MAX_HEX   ; /* __INTL_BIT_PARTS__ / 4 + 1 */

        BIT_PARTS = tb;
        U32_PARTS = BIT_PARTS >> (5);
        U16_PARTS = BIT_PARTS >> (4);
        MAX_BIN = BIT_PARTS + 3; // "0b" + '\0'
        MAX_DEC = MAX_BIN; // First apply as MAX bin maximum value, and then according to the actual calculation
        MAX_HEX = (BIT_PARTS >> (2)) + 3; // "0x" + '\0'

        /* The current configuration is applied */
        if (sizeof(temp) == (bits >> 3))
        {
            char buffer[__INTL_P_MAX_BIN__];
            MAX_DEC = intl_print(intl_shl(intl(1), BIT_PARTS - 1), buffer, sizeof(buffer), "d") + 1; // '\0'
        }

        fprintf(output, "#define __INTL_BIT_PARTS__                  %u"NEWLINE, BIT_PARTS);
        fprintf(output, "#define __INTL_U32_PARTS__                  %u"NEWLINE, U32_PARTS);
        fprintf(output, "#define __INTL_U16_PARTS__                  %u"NEWLINE, U16_PARTS);
        fprintf(output, "#define __INTL_P_MAX_BIN__                  %u"NEWLINE, MAX_BIN);
        fprintf(output, "#define __INTL_P_MAX_DEC__                  %u"NEWLINE, MAX_DEC);
        fprintf(output, "#define __INTL_P_MAX_HEX__                  %u"NEWLINE, MAX_HEX);

        /* __INTL_MAX__ */
        fprintf(output, "#define __INTL_MAX__                        (intl){.u32={");
        for (uint32_t i = 0; i < U32_PARTS; i++)
        {
            fprintf(output, "%s,", (i == U32_PARTS-1) ? "0x7FFFFFFF" : "-1");
        }
        fprintf(output, "}}"NEWLINE);

        /* __INTL_MIN__ */
        fprintf(output, "#define __INTL_MIN__                        (intl){.u32={");
        for (uint32_t i = 0; i < U32_PARTS; i++)
        {
            fprintf(output, "%s,", (i == U32_PARTS-1) ? "0x80000000" : "0");
        }
        fprintf(output, "}}"NEWLINE);

        /* __INTL_ZERO__ */
        fprintf(output, "#define __INTL_ZERO__                       (intl){.u32={");
        for (uint32_t i = 0; i < U32_PARTS; i++)
        {
            fprintf(output, "0,");
        }
        fprintf(output, "}}"NEWLINE);
    }

    fprintf(output, "#endif"NEWLINE);

    fprintf(output, "/*****************************************************************/"NEWLINE);
    fprintf(output, "/*                          Config end                           */"NEWLINE);
    fprintf(output, "/*****************************************************************/"NEWLINE);

    fprintf(stdout, "\r\n\r\n-------------------------------------------------------------\r\n"NEWLINE);

    /* The current configuration is not applied */
    if (sizeof(temp) != (bits >> 3))
    {
        fprintf(stdout, "[TODO][%s] Apply the current configuration and run it again to get the new `__INTL_P_MAX_DEC__`"NEWLINE, filename);
    }
    else  
    {
        fprintf(stdout, "[INFO][%s] The configuration has been generated, copy it to the `intl_cfg.h` range specified"NEWLINE, filename);
    }

    if (filename != NULL) 
    {
        fclose(output);
    }
}

static void test_define(void)
{
    intl a = intl(0);
    intl b = intl(10);
    intl c = intl(-10);
    intl d = intl(0xFF);

    intl e = intl_from("0");
    intl f = intl_from("100");
    intl g = intl_from("-100");
    intl h = intl_from("123456789123456789123456789");
    intl i = intl_from("0b1110000001111100101010100");
    intl j = intl_from("0o1236541236763210233642166");
    intl k = intl_from("0xF125E3F6D743648EEFFF12356");

    intl max = INTL_MAX;
    intl min = INTL_MIN;
    intl n0 = INTL_ZERO;
}

static void test_print(void)
{
    intl a = intl_from("123456789123456789123456789");

    printf("a: %s\r\n", intl_show(a, pbuff, sizeof(pbuff), "dx"));
    printf("a: %s\r\n", intl_show(INTL_ZERO, pbuff, sizeof(pbuff), "#x"));
    printf("a: %s\r\n", intl_show(a, pbuff, sizeof(pbuff), "#o"));
    printf("a: %s\r\n", intl_show(a, pbuff, sizeof(pbuff), "#x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "10x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "010x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "-10x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "-010x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#10x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#010x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#-10x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#-010x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#x"));
    printf("a: %s\r\n", intl_show(intl(123456), pbuff, sizeof(pbuff), "#0x"));
    printf("a: %s\r\n", intl_show(intl(-123456), pbuff, sizeof(pbuff), "#d"));
    printf("a: %s\r\n", intl_show(intl(-123456), pbuff, sizeof(pbuff), "#10d"));
    printf("a: %s\r\n", intl_show(intl(-123456), pbuff, sizeof(pbuff), "-#d"));
}

static void test_calculate(void)
{
    intl a = intl_from("123456789123456789123456789");
    intl b = intl_from("987654321987654321987654321");

    printf("a: %s\r\n",         intl_show(a, pbuff, sizeof(pbuff), "d"));
    printf("b: %s\r\n",         intl_show(b, pbuff, sizeof(pbuff), "d"));
    printf("a + b: %s\r\n",     intl_show(intl_add(a, b), pbuff, sizeof(pbuff), "d"));
    printf("a - b: %s\r\n",     intl_show(intl_sub(a, b), pbuff, sizeof(pbuff), "d"));
    printf("a * b: %s\r\n",     intl_show(intl_mul(a, b), pbuff, sizeof(pbuff), "d"));
    printf("b / a: %s\r\n",     intl_show(intl_div(a, b), pbuff, sizeof(pbuff), "d"));
    printf("b %% a: %s\r\n",    intl_show(intl_mod(a, b), pbuff, sizeof(pbuff), "d"));
    printf("a & b: %s\r\n",     intl_show(intl_and(a, b), pbuff, sizeof(pbuff), "d"));
    printf("a | b: %s\r\n",     intl_show(intl_or(a, b), pbuff, sizeof(pbuff), "d"));
    printf("a ^ b: %s\r\n",     intl_show(intl_xor(a, b), pbuff, sizeof(pbuff), "d"));
    printf("~a: %s\r\n",        intl_show(intl_not(a), pbuff, sizeof(pbuff), "d"));
    printf("a << 1: %s\r\n",    intl_show(intl_shl(a, 1), pbuff, sizeof(pbuff), "d"));
    printf("b >> 1: %s\r\n",    intl_show(intl_shr(a, 1), pbuff, sizeof(pbuff), "d"));
    printf("a++: %s\r\n",       intl_show(intl_inc(a), pbuff, sizeof(pbuff), "d"));
    printf("b--: %s\r\n",       intl_show(intl_dec(a), pbuff, sizeof(pbuff), "d"));
    printf("a > b: %d\r\n",     intl_gt(a, b));
    printf("a >= b: %d\r\n",    intl_ge(a, b));
    printf("a < b: %d\r\n",     intl_lt(a, b));
    printf("a <= b: %d\r\n",    intl_le(a, b));
    printf("a == b: %d\r\n",    intl_eq(a, b));
    printf("a != b: %d\r\n",    intl_ne(a, b));
}

static void test_base(void)
{
    // test_define();
    // test_print();
    test_calculate();
}

/************************************************************************************/
/*************************************  Command  ************************************/
/************************************************************************************/

static void usage(void)
{
    printf(
"Usage: intl [opt] [arg] ...\n"
"\n"
"options:\n"
"    -e <execute>        Specifies the function to execute, the default is the <base> test\n"
"                        <base>      Test base function\n"
"                        <ut>        Unit test\n"
"                        <define>    Test define function\n"
"                        <cal>       Calculate string math expression\n"
"                        <gen>       Generate the intl configuration file code segment, need specify -f -b\n"
"                        <print>     Print an intl number\n"
"    -l <format>         Format string, 10d, 08x, ...\n"
"    -o <op function>    Operate function, add, sub, mul, div, ...\n"
"    -n <intl>           intl number expression\n"
"    -f <filename>       File name, temporarily store configuration code segment\n"
"    -b <bits>           Maximum number of configured bits\n"
"    -h                  Print help\n"
"    -v                  Print version\n"
"    -u [<period>]       Unit test period, unit ms, the default is 1000ms\n"
"\n"

    );
}

static int test(int argc, char *argv[])
{
    char *execute = NULL;
    int ut_period = 1000;
    char *filename = NULL;
    int bits = 0;
    
    intl n[2] = {INTL_ZERO, INTL_ZERO};
    int in = 0;
    char *op = NULL;
    char *format = NULL;

    /* reset getopt */
    command_opt_init();

    while (1)
    {
        int opt = command_getopt(argc, argv, "e:hvu::f:b:n:o:l:");
        if (opt == -1) break;

        switch (opt) 
        {
        case 'l' :
            format = command_optarg;
            break;
        case 'o' :
            op = command_optarg;
            break;
        case 'n' :
            if (in < 2) n[in++] = intl_from(command_optarg);
            break;
        case 'b' :
            bits = atoi(command_optarg);
            break;
        case 'f' :
            filename = command_optarg;
            break;
        case 'u' :
            if (command_optarg) ut_period = atoi(command_optarg);
            break;
        case 'e' :
            execute = command_optarg;
            break;
        case 'v' :
            printf("intl version %d.%d.%d\r\n", INTL_V_MAJOR, INTL_V_MINOR, INTL_V_PATCH);
            return 0;
        case '?':
            printf("Unknown option `%c`\r\n", command_optopt);
            return -1;
        case 'h' : 
        default:
            usage();
            return 0;
        }
    }

    if (execute)
    {
        if (!strcmp(execute, "base"))
        {
            test_base();
        }
        else if (!strcmp(execute, "ut"))
        {
            srand((unsigned int)time(NULL));
            #if defined(TEST_TARGET_intl)
            while (1)
            {
                unitt_task();
                usleep(1000 * ut_period);
            }
            #else  
            printf("create task %d\r\n", task_create(ut_period, unitt_task));
            #endif
        }
        else if (!strcmp(execute, "define"))
        {
            test_define();
        }
        else if (!strcmp(execute, "gen"))
        {
            intl_gen_cfg(bits, filename);
        }
        else if (!strcmp(execute, "cal"))
        {
            intl result = INTL_ZERO;

            if      (!strcmp(op, "add")) result = intl_add(n[0], n[1]);
            else if (!strcmp(op, "sub")) result = intl_sub(n[0], n[1]);
            else if (!strcmp(op, "mul")) result = intl_mul(n[0], n[1]);
            else if (!strcmp(op, "div")) result = intl_div(n[0], n[1]);
            else if (!strcmp(op, "mod")) result = intl_mod(n[0], n[1]);
            else if (!strcmp(op, "and")) result = intl_and(n[0], n[1]);
            else if (!strcmp(op, "xor")) result = intl_xor(n[0], n[1]);
            else if (!strcmp(op, "or"))  result = intl_or(n[0], n[1]);
            else if (!strcmp(op, "shl")) result = intl_shl(n[0], *(uint32_t *)(&n[1]));
            else if (!strcmp(op, "shr")) result = intl_shr(n[0], *(uint32_t *)(&n[1]));
            else printf("No such function!\r\n");

            printf("result: %s\r\n", intl_show(result, pbuff, sizeof(pbuff), "d"));
        }
        else if (!strcmp(execute, "print"))
        {
            printf("%s\r\n", intl_show(n[0], pbuff, sizeof(pbuff), format));
        }
    }
    else  
    {
        test_base();
    }
    
    return 0;
}

/************************************************************************************/
/************************************ Test entry ************************************/
/************************************************************************************/

#if defined(TEST_TARGET_intl)
int main(int argc, char *argv[])
{
    return test(argc, argv);
}
#else 
void test_intl(void)
{
    command_export("intl", test);
}
init_export_app(test_intl);
#endif 
